Generated code - Using the EntityView2<T> class, Adapter
Preface
The
EntityView2<T> is a class which is used to create in-memory views on an
EntityCollection<T> object and allows you to filter and
sort an in-memory
EntityCollection<T> without actually touching the data inside the
EntityCollection<T>. An
EntityCollection<T> can have multiple
EntityView2<T> objects, similar to
the DataTable - DataView combination.
This section describes how to use the
EntityView2<T> class in various different scenarios.
EntityView2<T> and Linq to Objects
EntityView2<T> implements
IEnumerable<T> which allows you
to access its contents in foreach loops or for example in Linq to Object
queries. This enables you to create specific views on a larger
EntityCollection<T> which are used in different processing methods
utilizing Linq to Objects over the views created. It can be preferable to
use
EntityView2<T> objects instead of a Linq to Objects query over
the
EntityCollection<T> object as views are kept in-sync with the
underlying data, a linq query's results isn't.
"
DataBinding and EntityView2<T> instances
The
EntityCollection<T> class doesn't bind directly to a bound control, it always binds through its
EntityView2<T> object
(returned by the property
DefaultView, see below).
The
EntityView2<T> approach allows you to create multiple
EntityView2<T>
instances on a single
EntityCollection<T> and all bind them to different controls as
if they're different sets of data.
Creating an EntityView2<T> instance
Creating an
EntityView2<T> object is straightforward:
// C#
EntityCollection<CustomerEntity> customers = new EntityCollection<CustomerEntity>();
using(DataAccessAdapter adapter = new DataAccessAdapter())
{
adapter.FetchEntityCollection(customers, null); // fetch all customers
}
EntityView2<CustomerEntity> customerView = new EntityView2<CustomerEntity>(customers);
' VB.NET
Dim customers As New EntityCollection(Of CustomerEntity)()
Using adapter As New DataAccessAdapter()
adapter.FetchEntityCollection(customers, Nothing) ' fetch all customers
End Using
Dim customerView As New EntityView2(Of CustomerEntity)(customers)
This creates an
EntityView2<T> object on the
EntityCollection<T>
customers, so it lets you
view the data in the
EntityCollection<T> 'customers'.
EntityView2<T> objects don't contain any data: all data you'll be able to access through an
EntityView2<T> is actually data residing in the related
EntityCollection<T>.
You can also use the
EntityCollection<T>'s DefaultView property to create an
EntityView2<T>. This is similar to the DataTable's DefaultView property: every time you
read the property, you'll get the same view object back. This is also true for the
EntityCollection<T>'s
DefaultView property.
The
DefaultView property calls the virtual method
CreateDefaultEntityView()
which you can override to customize the
EntityView2<T> instance created for '
DefaultView'.
Instead of using the
EntityView2<T> class, you can use the
IEntityView2 interface, if you for example don't know the generic type.
The
EntityView2<T> constructor has various overloads which let you
specify an initial filter and / or sort expression. You can also set the
filter and / or sort expression later on as described below.
Filtering and sorting an EntityView2<T>
The purpose of an
EntityView2<T> is to give you a 'view' based on a filter and / or a sort-expression on an in-memory
EntityCollection<T>. Which entities from the related
EntityCollection<T>
is available to you through a particular
EntityView2<T> object depends on the filter
used for the
EntityView2<T>. In which order the
entities are available to you is controlled by the used sort expression. As the related
EntityCollection<T> is not touched, you can have as many
EntityView2<T> objects
on the same
EntityCollection<T>, all exposing different subsets of the data in the
EntityCollection<T>, in different order.
Filtering and sorting an
EntityView2<T> is done through normal LLBLGen Pro predicate and sort-clause classes. See for more information about predicate classes:
Getting started with filtering and
The predicate system.
The following example filters the aforementioned customers collection on all customers from the UK:
// C#
customerView.Filter = (CustomerFields.Country == "UK");
' VB.NET
customerView.Filter = (CustomerFields.Country = "UK")
You could also have specified this filter with the
EntityView2<T> constructor. As soon as the
EntityView2<T>'s
Filter property is set to a value, the
EntityView2<T>
object resets itself and will apply the new filter to the related
EntityCollection<T>
and all matching entity objects will be available through the
EntityView2<T>
object.
The
EntityView2<T>'s sorter uses the same system. Let's sort our filtered
EntityView2<T> on 'CompanyName', ascending. For more information about sort-clauses and
sortexpression objects, please see:
Generated code - Sorting.
// C#
customerView.Sorter = new SortExpression(CustomerFields.CompanyName | SortOperator.Ascending);
' VB.NET
customerView.Sorter = New SortExpression(CustomerFields.CompanyName Or SortOperator.Ascending)
Use a Predicate<T> or Lambda expression for a filter
EntityView2<T> has a couple of constructors which accept a lambda
expression or Predicate<T> object as a filter instead of an LLBLGen Pro
predicate object. The example below filters the passed in collection of
CustomerEntity instances on the
Country property:
var customersFromGermany =
new EntityView2<CustomerEntity>(customers, c=>c.Country=="Germany");
Dim customersFromGermany = _
New EntityView2(Of CustomerEntity)(customers, Function(c) c.Country="Germany")
Using
DelegatePredicate<T>, a developer can also use a
Predicate<T> delegate or Lambda expression to filter the
EntityView2<T> instance after it's been created:
var customersFromGermany =
new EntityView2<CustomerEntity>(customers);
customersFromGermany.Filter = new DelegatePredicate<CustomerEntity>(c=>c.Country=="Germany");
Dim customersFromGermany = _
New EntityView2(Of CustomerEntity)(customers)
customersFromGermany.Filter = New DelegatePredicate(Of CustomerEntity)(Function(c) c.Country="Germany")
Multi-clause sorting
The
EntityView2<T> allows you to sort the data using a SortExpression which
makes sorting on multiple fields at once possible. The following example
sorts
customerView on City ascending and on CompanyName descending:
// C#
ISortExpression sorter = new SortExpression(CustomerFields.City | SortOperator.Ascending);
sorter.Add(CustomerFields.CompanyName | SortOperator.Descending);
customerView.Sorter = sorter;
' VB.NET
Dim sorter As New SortExpression(CustomerFields.City Or SortOperator.Ascending)
sorter.Add(CustomerFields.CompanyName Or SortOperator.Descending)
customerView.Sorter = sorter
If you want to sort on a property which isn't related to an entity field, you've to use the class
EntityProperty instead of an entity field. So if you instead of sorting on CompanyName, want to sort on the
entity property
IsDirty, to get all the changed entities first, and then the non-changed entities, you've to use this code instead:
// C#
ISortExpression sorter = new SortExpression(CustomerFields.City | SortOperator.Ascending);
sorter.Add(new EntityProperty("IsDirty") | SortOperator.Ascending);
customerView.Sorter = sorter;
' VB.NET
Dim sorter As New SortExpression(CustomerFields.City Or SortOperator.Ascending)
sorter.Add(New EntityProperty("IsDirty") Or SortOperator.Descending)
customerView.Sorter = sorter
EntityProperty is usable in any construct which works with an entity field, as long as it's in-memory sorting or filtering. Below you'll learn how to filter
an
EntityView2<T>'s data using an entity property.
Filtering using multiple predicates
To filter the customers collection on all customers from the UK which
entities have been changed, use the following code below. It also
illustrates the usage of
EntityProperty again: it filters on a
property which isn't an entity field. Keep in mind that not all
predicate classes are usable for in-memory filtering: please consult the section
Generated code - The predicate system which classes are usable.
// C#
IPredicateExpression filter = new PredicateExpression(CustomerFields.Country == "UK");
filter.AddWithAnd(new EntityProperty("IsDirty") == true);
customerView.Filter = filter;
' VB.NET
Dim filter As New PredicateExpression(CustomerFields.Country = "UK")
filter.AddWithAnd(New EntityProperty("IsDirty") = True)
customerView.Filter = filter
View behavior on collection changes
When an entity changes in the related
EntityCollection<T> of the
EntityView2<T>, it can be the entity doesn't match the filter set for the view
anymore and the
EntityView2<T> therefore removes the entity from itself: it's no longer available to you through the
EntityView2<T>.
As it might be desirable to control when and how this behavior is enforced
by the
EntityView2<T>, it's configurable by specifying a
PostCollectionChangeAction value with the
EntityView2<T> constructor or by setting the
EntityView2<T>'s
DataChangeAction property.
The following list describes the various values and their result on the
EntityView2<T>'s behavior:
- NoAction (do nothing), i.e.: don't re-apply the filter nor the sorter.
- ReapplyFilterAndSorter (default). Reapplies the filter and sorter on the collection.
- ReapplySorter. Reapplies the sorter on the collection, not the filter.
By default, the
EntityView2<T> will re-apply the filter and sorter. There's no setting for just the filter, as re-applying the filter could alter the set
of entities in the view, which could
change the order of the data as in: it's no longer ordered and has to be re-sorted. If the related collection fires a reset event (when it is sorted using
its own code or cleared), the view is also reset and filters are re-applied as well as sorters.
If a new entity is added to the collection through code, it is not added to the view in
NoAction mode or in
ReapplySorter mode, because no filter is re-applied.
If it's added through data-binding, it actually
is added to the view, as it is added through the
EntityView2<T>, because an
EntityCollection<T> is bound to
a bound control via an
EntityView2<T>, either an
EntityView2<T> object you created and bound directly or through the
EntityView2<T> object returned by the
EntityCollection<T>'s
DefaultView property.
Projecting data inside an EntityView2<T> on another data-structure
A powerful feature of the
EntityView2<T> class is the ability to
project the data in the
EntityView2<T> onto a new data-structure, like an
EntityCollection<T>,
DataTable or custom classes. Projections are a way to produce custom lists of data ('dynamic lists in memory') based on the current data
in the
EntityView2<T> and a collection of
projection objects. Projection objects are small objects which specify which entity field or entity property
should be used in the projection and where to get the value from.
For example, because the raw projection data can be used to
re-instantiate new entities, the data can be used to produce a new
EntityCollection<T> with new entities. How the data is projected depends on the projection
engine used for the actual projection. For more information about projections please also see:
Fetching DataReaders and projections.
Projections are performed by applying a set of projection objects onto an entity and then by passing on the result data array for further storage to a
projection engine, or projector, the projected data is placed in a new instance of a class, for example an entity class, but this can also be a DataRow
or a custom class. The array is an array of type object. You can use filters during the
projection as well, to limit the set of data you want to project from the
EntityView2<T> data.
Projection objects: EntityPropertyProjector
A projection object is an instance of the
EntityPropertyProjector
class and it defines how to project a single entity field in an entity.
An
EntityPropertyProjector instance contains at most two IEntityFieldCore instances (e.g.
a normal entity field objects or an
EntityProperty object)
and an optional
Predicate, e.g. a
FieldCompareValuePredicate, or a
PredicateExpression. The first
IEntityFieldCore instance is mandatory. This is the default value to
use for the projection.
If a
Predicate is specified, and it resolves to true, the default value (thus the first IEntityFieldCore) is used
as value to use for the projection, otherwise
the second IEntityFieldCore instance.
The
EntityPropertyProjector also contains a
Name property which is used to produce the name of the result field. The projection
routine used is free to use this name for column purpose (e.g. during a projection onto a
DataTable) but can also use it for entity field setting (projection onto an entity).
If a developer wants to execute a piece of code to manipulate the value to
use for the projection, prior to storing it into the projected slot, the developer can derive his own class from
EntityPropertyProjector and override
ValuePostProcess(). This
routine is normally empty and expects the value and the entity being
processed.
Projecting an
EntityView2<T>'s data is done by the
CreateProjection
method of an
EntityView2<T> object. LLBLGen Pro comes with three different projection
engines: one for projecting data onto a DataTable (the class
DataProjectorToDataTable), one for projecting data onto an
EntityCollection<T>
(the class
DataProjectorToEntityCollection) and one for projecting data onto a list of custom classes (the class
DataProjectorToCustomClass). You can write your own projection engine as well: implement the interface
IEntityDataProjector to be able to use
the engine in projections of
EntityView2<T> data. If you also want to use the same engine in projections of result-sets as discussed in
Fetching DataReaders and projections, you also should implement the similar interface
IGeneralDataProjector. Because the interfaces can re-use the actual projection engine logic, it's easy to re-use projection code for both projection mechanisms.
Only the data which is available to you through the
EntityView2<T> can be projected. You can't project nested data inside entities nor entity data not in the
EntityView2<T>. In that case, create a new
EntityView2<T> on the same
EntityCollection<T> using a different filter and project that
EntityView2<T> object instead.
Creating EntityPropertyProjector instances for all entity fields.
Sometimes you want to project all fields of a given entity and to avoid creating a lot of
EntityPropertyProjector objects if your entity has a lot of fields, you can use the shortcut method on
EntityFields2:
EntityFields2.
ConvertToProjectors(
EntityFieldsFactory.
CreateEntityFieldsObject(EntityType.
entitynameEntity))
This method will return List of
IEntityPropertyProjector objects, one for each entity field of the specified entity type.
The classes are used in the examples below.
Examples of EntityView2<T> projections
Projection to DataTable.
// C#
EntityCollection<CustomerEntity> customers = new EntityCollection<CustomerEntity>();
adapter.FetchEntityCollection(customers, null); // fetch all customers
// create a view of all customers in germany
EntityView2<CustomerEntity> customersInGermanyView = new EntityView2<CustomerEntity>( customers,
(CustomerFields.Country == "Germany"), null );
// create projection of these customers of just the city and the customerid.
// for that, define 2 propertyprojectors, one for each field to project
List<IEntityPropertyProjector> propertyProjectors= new List<IEntityPropertyProjector>();
propertyProjectors.Add( new EntityPropertyProjector( CustomerFields.City, "City" ) );
propertyProjectors.Add( new EntityPropertyProjector( CustomerFields.CustomerId, "CustomerID" ) );
DataTable projectionResults = new DataTable();
// create the actual projection.
customersInGermanyView.CreateProjection( propertyProjectors, projectionResults );
' VB.NET
Dim customers As New EntityCollection(Of CustomerEntity)()
adapter.FetchEntityCollection(customers, nothing) ' fetch all customers
' create a view of all customers in germany
Dim customersInGermanyView As New EntityView2(Of CustomerEntity)( customers, _
New FieldCompareValuePredicate(CustomerFields.Country, Nothing, ComparisonOperator.Equal, "Germany"), Nothing)
' create projection of these customers of just the city and the customerid.
' for that, define 2 propertyprojectors, one for each field to project
Dim propertyProjectors As New List(Of IEntityPropertyProjector)()
propertyProjectors.Add( New EntityPropertyProjector( CustomerFields.City, "City" ) )
propertyProjectors.Add( New EntityPropertyProjector( CustomerFields.CustomerId, "CustomerID" ) )
Dim projectionResults As New DataTable()
' create the actual projection.
customersInGermanyView.CreateProjection( propertyProjectors, projectionResults )
After this code, the DataTable
projectionResults contains two columns, City and CustomerID, and it contains the data for the fields City and CustomerId of
each entity in the
EntityView2<T>, which are all entities with Country equal to "Germany".
Projection to EntityCollection
The following example performs a projection onto an EntityCollection. 'Clerk' is another subtype of
'Employee'.
// C#
// fetch all managers
EntityCollection<ManagerEntity> managers = new EntityCollection<ManagerEntity>();
adapter.FetchEntityCollection(managers, null);
// now project them onto 2 new clerk entities, by just projecting the employee fields
List<IEntityPropertyProjector> propertyProjectors = new List<IEntityPropertyProjector>();
propertyProjectors.Add( new EntityPropertyProjector( EmployeeFields.Id, "Id" ) );
propertyProjectors.Add( new EntityPropertyProjector( EmployeeFields.Name, "Name" ) );
propertyProjectors.Add( new EntityPropertyProjector( EmployeeFields.StartDate, "StartDate" ) );
propertyProjectors.Add( new EntityPropertyProjector( EmployeeFields.WorksForDepartmentId, "WorksForDepartmentId" ) );
EntityCollection<ClerkEntity> clerks = new EntityCollection<ClerkEntity>();
EntityView2<ManagerEntity> managersView = managers.DefaultView;
// project data to transform all managers into clerks. ;)
managersView.CreateProjection( propertyProjectors, clerks );
' VB.NET
' fetch all managers
Dim managers As New EntityCollection(Of ManagerEntity)()
adapter.FetchEntityCollection(managers, Nothing)
' now project them onto 2 new clerk entities, by just projecting the employee fields
Dim propertyProjectors As New List(Of IEntityPropertyProjector)()
propertyProjectors.Add( New EntityPropertyProjector( EmployeeFields.Id, "Id" ) )
propertyProjectors.Add( New EntityPropertyProjector( EmployeeFields.Name, "Name" ) )
propertyProjectors.Add( New EntityPropertyProjector( EmployeeFields.StartDate, "StartDate" ) )
propertyProjectors.Add( New EntityPropertyProjector( EmployeeFields.WorksForDepartmentId, "WorksForDepartmentId" ) )
Dim clerks As New EntityCollection(Of ClerkEntity)()
Dim managersView As EntityView2(Of ManagerEntity) = managers.DefaultView
' project data to transform all managers into clerks. ;)
managersView.CreateProjection( propertyProjectors, clerks )
After the code above, the collection
clerks contains
ClerkEntity instances with only the
EmployeeEntity fields (inherited by
ClerkEntity from its base type
EmployeeEntity, which is also the base type of
ManagerEntity) filled with data.
projection to custom classes
The code below uses the class
TestCustomer which is given below the projection example code (in C#). The projection also shows how to project a property of an entity which
isn't an entity field, namely
IsDirty, using the
EntityProperty
class. In .NET 3.5 or higher, you can also use Linq to Objects to
achieve the same goal.
EntityView2<T> implements
IEnumerable<T>
and can be used as a sequence source in a linq query.
// C#
EntityCollection<CustomerEntity> customers = new EntityCollection<CustomerEntity>();
adapter.FetchEntityCollection(customers, null); // fetch all customers
EntityView2<CustomerEntity> allCustomersView = customers.DefaultView;
// projection to custom 'TestCustomer' classes
List<TestCustomer> customCustomers = new List<TestCustomer>();
DataProjectorToCustomClass<TestCustomer> customClassProjector =
new DataProjectorToCustomClass<TestCustomer>( customCustomers );
List<IEntityPropertyProjector> propertyProjectors = new List<IEntityPropertyProjector>();
propertyProjectors.Add( new EntityPropertyProjector( CustomerFields.CustomerId, "CustomerID" ) );
propertyProjectors.Add( new EntityPropertyProjector( CustomerFields.City, "City" ) );
propertyProjectors.Add( new EntityPropertyProjector( CustomerFields.CompanyName, "CompanyName" ) );
propertyProjectors.Add( new EntityPropertyProjector( CustomerFields.Country, "Country" ) );
propertyProjectors.Add( new EntityPropertyProjector( new EntityProperty("IsDirty"), "IsDirty" ) );
// create the projection
allCustomersView.CreateProjection( propertyProjectors, customClassProjector );
'--------------------------------------
// Alternative linq to objects variant.
var customCustomers = (from c in allCustomersView
select new TestCustomer
{
CustomerID = c.CustomerId, City = c.City,
CompanyName = c.CompanyName, Country = c.Country,
IsDirty = c.Dirty
}).ToList();
' VB.NET
Dim customers As New EntityCollection(Of CustomerEntity)()
adapter.FetchEntityCollection(customers, Nothing) ' fetch all customers
Dim allCustomersView As EntityView2(Of CustomerEntity) = customers.DefaultView
' projection to custom 'TestCustomer' classes
Dim customCustomers As New List(Of TestCustomer)()
Dim customClassProjector As New DataProjectorToCustomClass(Of TestCustomer)( customCustomers )
Dim propertyProjectors As New List(Of IEntityPropertyProjector)()
propertyProjectors.Add( New EntityPropertyProjector( CustomerFields.CustomerId, "CustomerID" ) )
propertyProjectors.Add( New EntityPropertyProjector( CustomerFields.City, "City" ) )
propertyProjectors.Add( New EntityPropertyProjector( CustomerFields.CompanyName, "CompanyName" ) )
propertyProjectors.Add( New EntityPropertyProjector( CustomerFields.Country, "Country" ) )
propertyProjectors.Add( New EntityPropertyProjector( new EntityProperty("IsDirty"), "IsDirty" ) )
' create the projection
allCustomersView.CreateProjection( propertyProjectors, customClassProjector )
'--------------------------------------
' Alternative linq to objects variant
var customCustomers = (From c In allCustomersView _
Select New TestCustomer With _
{
.CustomerID = c.CustomerId, .City = c.City, _
.CompanyName = c.CompanyName, .Country = c.Country, _
.IsDirty = c.Dirty _
}).ToList()
The custom class, TestCustomer:
/// <summary>
/// Test class for projection of fetched entities onto custom classes using a custom projector.
/// </summary>
public class TestCustomer
{
public TestCustomer()
{
}
#region Class Property Declarations
public string CustomerID { get; set;}
public string City { get; set; }
public string CompanyName { get; set;}
public string Country { get; set;}
public bool IsDirty { get; set; }
#endregion
}
Distinct projections.
It can be helpful to have distinct projections: no duplicate data exists in the projection results. Creating a distinct projection is simply passing false / False for
allowDuplicates in the
CreateProjection method.
The following example shows a couple of projection related aspects: it filters the entity view's data using a
Like predicate prior to projecting data, so you can
limit the data inside an
EntityView2<T> used for the projection, and it shows an example how
a predicate is used to choose between two values in an entity to determine the end result of projecting an entity. The example uses Northwind like most
examples in this documentation. The code contains Assert statements, which are left to show you how many elements to expect at that point in the
method.
EntityCollection<CustomerEntity> customers =
new EntityCollection<CustomerEntity>();
adapter.FetchEntityCollection( customers, null );
EntityView2<CustomerEntity> customersInGermanyView =
new EntityView2<CustomerEntity>( customers, (CustomerFields.Country == "Germany"), null );
Assert.AreEqual( 11, customersInGermanyView.Count );
// create straight forward projection of these customers of just the city and the customerid.
List<IEntityPropertyProjector> propertyProjectors= new List<IEntityPropertyProjector>();
propertyProjectors.Add( new EntityPropertyProjector( CustomerFields.City, "City" ) );
propertyProjectors.Add( new EntityPropertyProjector( CustomerFields.CustomerId, "CustomerID" ) );
DataTable projection = new DataTable();
customersInGermanyView.CreateProjection( propertyProjectors, projection );
Assert.AreEqual( 11, projection.Rows.Count );
// do distinct filtering during the following projection. It projects ContactTitle and IsNew
propertyProjectors = new List<IEntityPropertyProjector>();
propertyProjectors.Add( new EntityPropertyProjector( CustomerFields.ContactTitle, "Contact title" ) );
// any entity property can be used for projection source.
propertyProjectors.Add( new EntityPropertyProjector( new EntityProperty( "IsNew" ), "Is new" ) );
projection = new DataTable();
customersInGermanyView.CreateProjection( propertyProjectors, projection, false );
Assert.AreEqual( 7, projection.Rows.Count );
// do distinct filtering and filter the set to project. Re-use previous property projectors.
// 3 rows match the specified filter, distinct filtering makes it 2.
projection = new DataTable();
customersInGermanyView.CreateProjection( propertyProjectors, projection, false, (CustomerFields.ContactTitle % "Marketing%") );
Assert.AreEqual( 2, projection.Rows.Count );
// use alternative projection source based on filter.
projection = new DataTable();
propertyProjectors = new List<IEntityPropertyProjector>();
// bogus data, but performs what we need: for all contacttitles not matching the filter, CustomerId is used.
propertyProjectors.Add( new EntityPropertyProjector( CustomerFields.ContactTitle,
"Contact title", (CustomerFields.ContactTitle % "Marketing%"), CustomerFields.CustomerId) );
propertyProjectors.Add( new EntityPropertyProjector( CustomerFields.CustomerId, "CustomerID" ) );
// create a new projection, with distinct filtering, which gives different results now, because ContactTitle is now sometimes equal to CustomerId
customersInGermanyView.CreateProjection( propertyProjectors, projection, false );
Assert.AreEqual( 11, projection.Rows.Count );
foreach( DataRow row in projection.Rows )
{
if( !row["Contact title"].ToString().StartsWith( "Marketing" ) )
{
Assert.AreEqual( row["Contact title"], row["CustomerID"] );
}
}
Aggregates aren't supported in in-memory projections though Expressions are.
All expressions are fully evaluated, where '+' operators on strings result
in string concatenations. The
DbFunctionCall object to call database functions inside an
Expression object is ignored during expression evaluation.